package com.lipaluma.field.mapper.single.impl;

import java.lang.reflect.Field;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.BooleanUtils;
import org.apache.commons.lang.StringUtils;

import com.lipaluma.annotations.validations.ValidateBoolean;
import com.lipaluma.context.Context;
import com.lipaluma.field.mapper.mapping.FieldMappingInfo;
import com.lipaluma.field.mapper.mapping.MappingOptions;
import com.lipaluma.field.mapper.single.AbstractStandardSingleFieldMapper;
import com.lipaluma.field.validator.ChainValidator;
import com.lipaluma.field.validator.ValidationBasicOptions;
import com.lipaluma.field.validator.ValidatorFactory;
import com.lipaluma.xml.mapping.XmlFieldMapping;

/**
 * Class who maps a Boolean for a field of the target class.<br/>
 * This type of column is defined like standard but, he can have a more complexe configuration using the annotation {@link com.lipaluma.annotations.mapping.old.MapBoolean}<br/>
 * 
 * this annotation allows the user define the true values and the false values.
 * 
 * @author Mario
 */
public class BooleanFieldMapper extends AbstractStandardSingleFieldMapper {

	private String[] trueValues = null;
	private String[] falseValues = null;

	public BooleanFieldMapper() {}
	/**
	 * Field Mapper that parses a boolean
	 * @param mapping information of mapping
	 */
	public BooleanFieldMapper(FieldMappingInfo mapping) {
		super(mapping);
	}
	/**
	 * Field Mapper that parses a boolean
	 * @param trueValues the true values. Can be null
	 * @param falseValues the false values. Can be null
	 */
	public BooleanFieldMapper(String[] trueValues, String[] falseValues) {
		this.trueValues = trueValues;
		this.falseValues = falseValues;
	}
	/**
	 * Field Mapper that parses a boolean
	 * @param mapping information of mapping
	 * @param trueValues the true values. Can be null
	 * @param falseValues the false values. Can be null
	 */
	public BooleanFieldMapper(FieldMappingInfo mapping, String[] trueValues, String[] falseValues) {
		super(mapping);
		this.trueValues = trueValues;
		this.falseValues = falseValues;
		checkPreconditions();
	}

	@Override
	protected void init(MappingOptions mappingOptions) {
		this.trueValues = mappingOptions.getTrueValues(); 
		this.falseValues = mappingOptions.getFalseValues(); 
		checkPreconditions();
	}
	
	@Override
	public ChainValidator createChainValidator(Context context, Field field) {
		ValidationBasicOptions annotation = ValidationBasicOptions.builder().byAnnotation(field.getAnnotation(ValidateBoolean.class)).build();
		return ValidatorFactory.createNewChainValidator(context, field, annotation);
	}

	@Override
	public ChainValidator createChainValidator(Context context, Field field, XmlFieldMapping mapping) {
		return ValidatorFactory.createNewChainValidator(context, field, mapping);
	}

	@Override
	protected Object parseExtractedValue(String value) {
		if(ArrayUtils.isNotEmpty(trueValues) && ArrayUtils.isNotEmpty(falseValues)) {
			for (String trueValue : trueValues) {
				if(StringUtils.equals(value, trueValue))
					return true;
			}
			for (String falseValue : falseValues) {
				if(value == null || StringUtils.equals(value, falseValue))
					return false;
			}
			throw new RuntimeException("No boolean can be parsed");
		} else if(ArrayUtils.isNotEmpty(trueValues)) {
			for (String trueValue : trueValues) {
				if(StringUtils.equals(value, trueValue))
					return true;
			}
			return false;
		} else if(ArrayUtils.isNotEmpty(falseValues)) {
			for (String falseValue : falseValues) {
				if(value == null || StringUtils.equals(value, falseValue))
					return false;
			}
			return true;
		} else {
			return BooleanUtils.toBoolean(value);
		}
	}

	private void checkPreconditions() {
		if(trueValues != null && falseValues != null){
			for (String trueValue : trueValues) 
				for (String falseValue : falseValues) 
					if(trueValue.equals(falseValue))
						throw new IllegalStateException("All true values must be different than all false values");
		}		
	}
	
	@Override
	protected Object formatValueToWriteOnRow(Object value) {
		if(ArrayUtils.isNotEmpty(trueValues) && BooleanUtils.isTrue((Boolean)value))
			return trueValues[0];
		if(ArrayUtils.isNotEmpty(falseValues) && BooleanUtils.isFalse((Boolean)value))
			return falseValues[0];
		else 
			return value;
	}
}
